﻿/*
 * Copyright 2009 - 2010 eScience Institute
 *
 * This file is part of SqlShare.
 *
 * SqlShare is free software: you can redistribute it and/or modify it under the terms 
 * of the GNU General Public License as published by the Free Software Foundation, 
 * either version 3 of the License, or (at your option) any later version.
 *
 * SqlShare is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with 
 * SqlShare. If not, see http://www.gnu.org/licenses/.
*/

using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Windows.Threading;
using System.Text.RegularExpressions;

namespace TestSL3
{
    public partial class FileUpload : UserControl
    {
        private const string URI_HANDLER_PAGE = "FileAnalyzer.ashx";

        List<FileInfo> fileColl = new List<FileInfo>();
        FileInfo _file;
        Dispatcher UIDispatcher;
        string FileSize = ""; //to show while uploading
        Uri _hostURI;
        int ChunkSize = 4194304;
        long _dataLength;
        long _dataSent = 0;

        public FileUpload()
        {
            InitializeComponent();
            _hostURI = Application.Current.Host.Source;
        }        

        /// <summary>
        /// Retrieve the character delimiter from the user control, it should return either 1 char length string or 'tab'
        /// we use 'tab' instead of \t because we cannot pass it as a parameter via web query. Null if invalid parameter
        /// </summary>
        private string CharacterDelimiter
        {            
            get
            {
                ComboBoxItem item = (ComboBoxItem)cbDelimiter.SelectedItem;
                string selectedStr = (string)item.Content;
                return (selectedStr.Equals("tab") || selectedStr.Length == 1) ? selectedStr : null;
            }
        }

        private void EnableUI(bool enable)
        {
            cbColumnHeader.IsEnabled = enable;
            btnUpload.IsEnabled = enable;
            cbDelimiter.IsEnabled = enable;
        }

        private void InitializeUpload()
        {
            progressAnalyze.Value = 0;
            progressBulkInsert.Value = 0;
            progressUpload.Value = 0;
            UIDispatcher = this.Dispatcher;
            EnableUI(false);


            WriteStatus("Initializing new upload: " + _file.Name);
            txtMessage.Text = "Initializing..";
            FileSize = this.GetFileSize(_file.Length);
            _dataLength = _file.Length;
            StartHttpRequest("initialize");
        }
                

        private void StartHttpRequest(string mode) 
        {            
            string queryParams = string.Format("filename={0}&mode={1}", _file.Name, mode);

            if (mode.Equals("initialize"))
            {
                // pass parameters to handle this whole process
                queryParams += "&hasHeader=" + cbColumnHeader.IsChecked + "&delimiter=" + CharacterDelimiter + "&tablename=" + tbTableName.Text;
            }
            else if (mode.Equals("upload"))
            {
                long dataToSend = _dataLength - _dataSent;
                bool isLastChunk = dataToSend <= ChunkSize;
                bool isFirstChunk = _dataSent == 0;               

                queryParams += string.Format("&offset={0}&last={1}&first={2}", _dataSent, isLastChunk, isFirstChunk);
            }
            else if (mode.Equals("analyze") || mode.Equals("bulkinsert"))
            {
                // do nothing, just dont return on the next if else
            }
            else
            {
                // mode complete: just return, end of execution
                return;
            }

            UriBuilder httpHandlerUrlBuilder = GetUriBuilder(URI_HANDLER_PAGE);
            httpHandlerUrlBuilder.Query = (string.IsNullOrEmpty(httpHandlerUrlBuilder.Query) ? "" : httpHandlerUrlBuilder.Query.Remove(0, 1) + "&") + queryParams;
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(httpHandlerUrlBuilder.Uri);
            webRequest.Method = "POST";

            // if upload, jump to the async call so we can upload the file first before sending it down the pipe
            if (mode.Equals("upload"))
            {
                webRequest.BeginGetRequestStream(new AsyncCallback(WriteToStreamCallbackUpload), webRequest);
            }
            else
            {
                webRequest.BeginGetResponse(new AsyncCallback(ReadHttpResponseCallback), webRequest);
            }
        }
        

        private void WriteToStreamCallbackUpload(IAsyncResult asynchronousResult)
        {
            HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
            Stream requestStream = webRequest.EndGetRequestStream(asynchronousResult);

            Stream fileStream = _file.OpenRead();

            byte[] buffer = new Byte[4096];
            int bytesRead = 0;
            int tempTotal = 0;

            //Set the start position
            fileStream.Position = _dataSent;
           
            //Read the next chunk
            //&& !_file.IsDeleted && _file.State != Constants.FileStates.Error
            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0 && tempTotal + bytesRead < ChunkSize)
            {
                requestStream.Write(buffer, 0, bytesRead);
                requestStream.Flush();

                _dataSent += bytesRead;
                tempTotal += bytesRead;
            }
            fileStream.Close();
            fileStream.Dispose();
            requestStream.Close();

            // Bump this to the response handler 
            webRequest.BeginGetResponse(new AsyncCallback(ReadHttpResponseCallback), webRequest);

        }

        private void ReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {

            ResponseMessage msg = null;
            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);

                msg = ResponseMessage.ReadFromStream(webResponse.GetResponseStream());
                webResponse.Close();

                if (!string.IsNullOrEmpty(msg.Error))
                {
                    throw new Exception(msg.Error);
                }
                
                
                // update our progress bars
                UpdateShowProgress(msg);

                // 
                StartHttpRequest((string)msg.Params["mode"]);                
            }
            catch (Exception e)
            {
                UIDispatcher.BeginInvoke(delegate()
                {
                    ShowError(e.Message);
                    ResetGlobals();
                });
            }            
        }        

        private void UpdateShowProgress(ResponseMessage msg)
        {
            UIDispatcher.BeginInvoke(delegate()
            {

                double uploadWork = (double)_dataSent / (double)_dataLength;
                double analyzeWork = (double)msg.Params["work2"];
                double bulkWork = (double)msg.Params["work3"];

                if (progressUpload.Value == 0 && uploadWork > 0)
                {
                    txtMessage.Text = "Uploading file..";                    
                }
                if (progressUpload.Value < 1 && uploadWork == 1)
                {
                    txtMessage.Text = "Analyzing file contents..";
                    WriteStatus("File upload complete.");
                }
                else if (progressAnalyze.Value < 1 && analyzeWork == 1)
                {
                    txtMessage.Text = "Inserting data..";
                    WriteStatus("File analyze complete.");
                }
                else if (progressBulkInsert.Value < 1 && bulkWork == 1)
                {
                    txtMessage.Text = "Data Inserted Successful!";
                    WriteStatus("Data insert complete.");
                }

                progressUpload.Value = uploadWork;
                progressAnalyze.Value = analyzeWork;
                progressBulkInsert.Value = bulkWork;

                txtUpload.Text = "File Upload: " + string.Format("{0:###.00}%", uploadWork * 100D);
                txtAnalyze.Text = "Analyze: " + string.Format("   {0:###.00}%", analyzeWork * 100D);
                txtBulkInsert.Text = "Data inserted: " + string.Format("   {0:###.00}%", bulkWork * 100D);

                // all complete
                if (uploadWork == 1 && analyzeWork == 1 && bulkWork == 1)
                {
                    WriteStatus(_file.Name + " uploaded as table '" + msg.Params["tablename"] + "' rowcount: " + msg.Params["rows"]);
                    ResetGlobals();
                }
            });
        }

        private UriBuilder GetUriBuilder(string page)
        {
            Uri address = new Uri(_hostURI, "/" + page);
            return new UriBuilder(address.ToString());
        }      

        private void ResetGlobals()
        {
            _dataSent = 0;
            _dataLength = 0;
            FileSize = "";
            fileColl.Clear();            
            UIDispatcher = null;
            EnableUI(true);
        }

        private void WriteStatus(string text)
        {
            UIDispatcher.BeginInvoke(delegate() {
                tbStatus.Text += text + Environment.NewLine;
                tbStatusScrollViewer.ScrollToVerticalOffset(tbStatus.ActualHeight);
            });
            
        }

        private string GetFileSize(long length)
        {
            double bytes = (double)length;

            string fileSize = "0 KB";

            if (bytes >= 1073741824)
                fileSize = String.Format("{0:##.##}", bytes / 1073741824) + " GB";
            else if (bytes >= 1048576)
                fileSize = String.Format("{0:##.##}", bytes / 1048576) + " MB";
            else if (bytes >= 1024)
                fileSize = String.Format("{0:##.##}", bytes / 1024) + " KB";
            else if (bytes > 0 && bytes < 1024)
                fileSize = "1 KB";

            return fileSize;
        }
        

        private void ShowError(string errorTxt)
        {
            UIDispatcher.BeginInvoke(delegate()
            {
                tbStatus.Text += string.IsNullOrEmpty(tbStatus.Text) ? errorTxt : (Environment.NewLine + errorTxt + Environment.NewLine);
                tbStatusScrollViewer.ScrollToVerticalOffset(tbStatus.ActualHeight);
            });            
        }


        private void btnUpload_Click(object sender, RoutedEventArgs e)
        {
            // check constraint for character delimiter            
            if (CharacterDelimiter == null)
            {
                txtMessage.Text = "Error: Invalid character delimiter, please reselect character delimiter before continueing\n";
                return;
            }

            if (!IsValidName(tbTableName.Text))
            {
                txtMessage.Text = "Error: Invalid table name, cannot use characters. Please use  [a-zA-Z0-9] and _";
                return;
            }

            if (_file == null)
            {
                txtMessage.Text = "Error: Please choose a file first";
                return; 
            }
            
            InitializeUpload();
        }

        private void btnBrowse_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog fd = new OpenFileDialog();

            fd.Multiselect = false;

            if (fd.ShowDialog() == true)
            {
                _file = fd.File;
                tbSelectedFile.Text = _file.Name;
                tbTableName.Text = EscapeToName(_file.Name);
                btnUpload.IsEnabled = true;
            }
        }

        public static bool IsValidName(string name)
        {
            Regex pattern = new Regex(@"^\w*$");
            return pattern.IsMatch(name);                                    
        }

        public static string EscapeToName(string raw)
        {
            string[] charsToEscape = new string[] { " ", ".", "-", ",", "'", "\"", "[","]","(",")"};
            string word = raw.ToLower();
            int index = word.IndexOf('.');
            if (index > 0)
            {
                word = word.Substring(0, index);
            }

            foreach (string badChar in charsToEscape)
            {
                word = word.Replace(badChar, "_");
            }
            return word;
        }
    }
}
